package sdk

import (
	"dgo/goutils/utils"
	"dgo/goutils/yhttp"
	"fmt"
	"gitee.com/ymofen/supervalue"
	"io/ioutil"
	"time"
)

/**
  微信公众号SDK
*/

const (
	// https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
	VX_URL_API_GETTOKEN string = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s`

	// https://www.jianshu.com/p/911776d29a8e
	VX_URL_API_GETCODE string = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=%s#wechat_redirect`

	// https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#1
	VX_URL_API_GETOPENID_BYCODE string = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code`

	// https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#发送模板消息
	VX_URL_API_SEND_TEMPL_MESSAGE string = `https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s`
)

type VxUtil struct {
	AppId     string
	AppSecret string

	/**
	  基本配置中的Token
	  https://mp.weixin.qq.com/advanced/advanced?action=interface&t=advanced/interface&token=39710001&lang=zh_CN
	*/
	BaseConfigToken          string
	BaseConfigEncodingAESKey string

	accessToken         string
	accessTokenExp      time.Time
	accessTokenBlockExp time.Time
}

func NewVxUtil() *VxUtil {
	ins := &VxUtil{}
	return ins
}

/*
*

	{
	      "appid": "wxcabe9d879ff40cdd",
	      "appsecret": "2552cf7558f7df4118de0c199a9668b2"
	}
*/
func (this *VxUtil) ReloadConf(conf *wrapper.SuperValue) {
	if conf == nil {
		return
	}
	conf.CheckExistsStrFunc("appid", func(strv string) {
		this.AppId = strv
	})

	conf.CheckExistsStrFunc("appsecret", func(strv string) {
		this.AppSecret = strv
	})

	conf.CheckExistsStrFunc("baseconfig-token", func(strv string) {
		this.BaseConfigToken = strv
	})
}

func (this *VxUtil) GetURL4Code(redirecturl string, param string) string {
	return fmt.Sprintf(VX_URL_API_GETCODE, this.AppId, utils.QueryEscapeUrl(redirecturl), utils.QueryEscapeUrl(param))
}

/*
*
https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#1
*/
func (this *VxUtil) GetOpenIDByCode(code string) (json *wrapper.SuperValue, err error) {
	u0 := fmt.Sprintf(VX_URL_API_GETOPENID_BYCODE, this.AppId, this.AppSecret, code)
	resp, err := yhttp.DefaultHttpGet(u0)
	if err != nil {
		return nil, err
	} else {
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return nil, err
		}
		json, err := wrapper.NewSVFromBuf(body)
		if err != nil {
			return nil, fmt.Errorf("解析响应数据[%s]出现异常:%s", string(body), err.Error())
		}
		json.SetKeyValue("__url", u0)
		return json, nil
	}
}

/*
*

	{
	   "touser":"OPENID",   // 接收者openid
	   "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
	   "url":"http://weixin.qq.com/download",   // 模板跳转链接（海外帐号没有跳转能力）
	   "miniprogram":{   // 跳小程序所需数据，不需跳小程序可不用传该数据
		 "appid":"xiaochengxuappid12345",    // 所需跳转到的小程序appid（该小程序appid必须与发模板消息的公众号是绑定关联关系，暂不支持小游戏）
		 "pagepath":"index?foo=bar"    // 所需跳转到小程序的具体页面路径，支持带参数,（示例index?foo=bar），要求该小程序已发布，暂不支持小游戏
	   },
	   "data":{
			   "first": {
				   "value":"恭喜你购买成功！",
				   "color":"#173177"    // 模板内容字体颜色，不填默认为黑色
			   },
			   "keyword1":{
				   "value":"巧克力",
				   "color":"#173177"
			   },
			   "keyword2": {
				   "value":"39.8元",
				   "color":"#173177"
			   },
			   "keyword3": {
				   "value":"2014年9月22日",
				   "color":"#173177"
			   },
			   "remark":{
				   "value":"欢迎再次购买！",
				   "color":"#173177"
			   }
	   }
	}

注：url和miniprogram都是非必填字段，若都不传则模板无跳转；若都传，会优先跳转至小程序。开发者可根据实际需要选择其中一种跳转方式即可。当用户的微信客户端版本不支持跳小程序时，将会跳转至url。

	{
	    "errcode":0,
	     "errmsg":"ok",
	     "msgid":200228332
	}
*/
func (this *VxUtil) SendTemplMessage(data *wrapper.SuperValue) (err error) {
	err = this.checkAccessToken()
	if err != nil {
		return
	}

	u := fmt.Sprintf(VX_URL_API_SEND_TEMPL_MESSAGE, this.accessToken)
	respBuf, e1 := yhttp.DefaultHttpPostString(u, "application/json", data.AsJSONString(false))
	if e1 != nil {
		err = e1
		return
	}
	resp, e1 := wrapper.NewSVFromBuf(respBuf)
	if e1 != nil {
		err = fmt.Errorf("解析响应数据(%s)出现异常:%s", string(respBuf), e1.Error())
		return
	}
	errcode := resp.IntByName("errcode", 0)
	if errcode == 0 {
		return nil
	} else {
		return fmt.Errorf("发送模板消息出现异常,errcode:%d, %s", errcode, resp.StringByName("errmsg", ""))
	}
}

/*
*
获取token
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
*/
func (this *VxUtil) checkAccessToken() (rerr error) {
	if len(this.accessToken) != 0 && time.Now().Before(this.accessTokenExp) {
		return nil
	}

	if time.Now().Before(this.accessTokenBlockExp) {
		rerr = fmt.Errorf("IP被阻塞至[%s], 拒绝访问", utils.DateTimeString2(this.accessTokenBlockExp))
		return
	}

	resp, err := yhttp.DefaultHttpGet(fmt.Sprintf(VX_URL_API_GETTOKEN, this.AppId, this.AppSecret))
	if err != nil {
		rerr = err
		return
	} else {
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			rerr = err
			return
		}
		json, err := wrapper.NewSVFromBuf(body)
		if err != nil {
			rerr = fmt.Errorf("解析响应数据[%s]出现异常:%s", string(body), err.Error())
			return
		}
		errcode := json.Int64ByName("errcode", 0)
		if errcode == -1 {
			rerr = fmt.Errorf("系统繁忙，稍后再试:%s", json.StringByName("errmsg", ""))
			return
		}

		if errcode == 89507 { // 1小时内该IP被管理员拒绝调用一次，1小时内不可再使用该IP调用
			this.accessTokenBlockExp = time.Now().Add(time.Hour + time.Second*10)
			rerr = fmt.Errorf("IP被阻塞至[%s], 拒绝访问:%s", utils.DateTimeString2(this.accessTokenBlockExp), string(body))
			return
		}

		if errcode == 89506 { //24小时内该IP被管理员拒绝调用两次，24小时内不可再使用该IP调用
			this.accessTokenBlockExp = time.Now().Add(time.Hour*24 + time.Second*10)
			rerr = fmt.Errorf("IP被阻塞至[%s], 拒绝访问:%s", utils.DateTimeString2(this.accessTokenBlockExp), string(body))
			return
		}

		if errcode == 0 {
			this.accessToken = json.StringByName("access_token", "")
			if len(this.accessToken) == 0 {
				rerr = fmt.Errorf("获取acesstoken失败:%s", string(body))
				return
			}
			this.accessTokenExp = time.Now().Add(time.Duration(json.Int64ByName("expires_in", 7200)-10) * time.Second)
		} else {
			rerr = fmt.Errorf("获取acesstoken失败:%s", string(body))
			return
		}
	}
	return
}
